
/**
  ******************************************************************************
  * Copyright 2021 The Microbee Authors. All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  * 
  * http://www.apache.org/licenses/LICENSE-2.0
  * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * 
  * @file       bl_protocol.c
  * @author     baiyang
  * @date       2023-4-23
  ******************************************************************************
  */

// bootloader flash update protocol.
//
// Command format:
//
//      <opcode>[<command_data>]<EOC>
//
// Reply format:
//
//      [<reply_data>]<INSYNC><status>
//
// The <opcode> and <status> values come from the PROTO_ defines below,
// the <*_data> fields is described only for opcodes that transfer data;
// in all other cases the field is omitted.
//
// Expected workflow (protocol 3) is:
//
// GET_SYNC        verify that the board is present
// GET_DEVICE        determine which board (select firmware to upload)
// CHIP_ERASE        erase the program area and reset address counter
// loop:
//      PROG_MULTI      program bytes
// GET_CRC        verify CRC of entire flashable area
// RESET        finalise flash programming, reset chip and starts application
//

/*----------------------------------include-----------------------------------*/
#include "hw_config.h"

#include <inttypes.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>

#include "support.h"
#include "bl_protocol.h"
/*-----------------------------------macro------------------------------------*/
#define BL_PROTOCOL_VERSION         5        // The revision of the bootloader protocol
//* Next revision needs to update

// protocol bytes
#define PROTO_INSYNC                0x12    // 'in sync' byte sent before status
#define PROTO_EOC                   0x20    // end of command

// Reply bytes
#define PROTO_OK                    0x10    // INSYNC/OK      - 'ok' response
#define PROTO_FAILED                0x11    // INSYNC/FAILED  - 'fail' response
#define PROTO_INVALID               0x13    // INSYNC/INVALID - 'invalid' response for bad commands
#define PROTO_BAD_SILICON_REV       0x14     // On the F4 series there is an issue with < Rev 3 silicon
#define PROTO_RESERVED_0X15         0x15  // Reserved

// see https://pixhawk.org/help/errata
// Command bytes
#define PROTO_GET_SYNC              0x21    // NOP for re-establishing sync
#define PROTO_GET_DEVICE            0x22    // get device ID bytes
#define PROTO_CHIP_ERASE            0x23    // erase program area and reset program address
#define PROTO_PROG_MULTI            0x27    // write bytes at program address and increment
#define PROTO_GET_CRC               0x29    // compute & return a CRC
#define PROTO_GET_OTP               0x2a    // read a byte from OTP at the given address
#define PROTO_GET_SN                0x2b    // read a word from UDID area ( Serial)  at the given address
#define PROTO_GET_CHIP              0x2c    // read chip version (MCU IDCODE)
#define PROTO_SET_DELAY             0x2d    // set minimum boot delay
#define PROTO_GET_CHIP_DES          0x2e    // read chip version In ASCII
#define PROTO_BOOT                  0x30    // boot the application
#define PROTO_DEBUG                 0x31    // emit debug information - format not defined
#define PROTO_SET_BAUD              0x33    // set baud rate on uart

#define PROTO_RESERVED_0X36     0x36  // Reserved
#define PROTO_RESERVED_0X37     0x37  // Reserved
#define PROTO_RESERVED_0X38     0x38  // Reserved
#define PROTO_RESERVED_0X39     0x39  // Reserved

#define PROTO_PROG_MULTI_MAX    64    // maximum PROG_MULTI size
#define PROTO_READ_MULTI_MAX    255   // size of the size field

/* argument values for PROTO_GET_DEVICE */
#define PROTO_DEVICE_BL_REV      1    // bootloader revision
#define PROTO_DEVICE_BOARD_ID    2    // board ID
#define PROTO_DEVICE_BOARD_REV   3    // board revision
#define PROTO_DEVICE_FW_SIZE     4    // size of flashable area
#define PROTO_DEVICE_VEC_AREA    5    // contents of reserved vectors 7-10

// State
#define STATE_PROTO_GET_SYNC      0x1     // Have Seen NOP for re-establishing sync
#define STATE_PROTO_GET_DEVICE    0x2     // Have Seen get device ID bytes
#define STATE_PROTO_CHIP_ERASE    0x4     // Have Seen erase program area and reset program address
#define STATE_PROTO_PROG_MULTI    0x8     // Have Seen write bytes at program address and increment
#define STATE_PROTO_GET_CRC       0x10    // Have Seen compute & return a CRC
#define STATE_PROTO_GET_OTP       0x20    // Have Seen read a byte from OTP at the given address
#define STATE_PROTO_GET_SN        0x40    // Have Seen read a word from UDID area ( Serial)  at the given address
#define STATE_PROTO_GET_CHIP      0x80    // Have Seen read chip version (MCU IDCODE)
#define STATE_PROTO_GET_CHIP_DES  0x100   // Have Seen read chip version In ASCII
#define STATE_PROTO_BOOT          0x200   // Have Seen boot the application

#if defined(TARGET_HW_PX4_PIO_V1) || defined(TARGET_HW_MODALAI_VOXL2_IO)
#define STATE_ALLOWS_ERASE        (STATE_PROTO_GET_SYNC)
#define STATE_ALLOWS_REBOOT       (STATE_PROTO_GET_SYNC)
#  define SET_BL_STATE(s)
#  define SET_BL_FIRST_STATE(s)   bl_state |= (s)
#else
#define STATE_ALLOWS_ERASE        (STATE_PROTO_GET_SYNC|STATE_PROTO_GET_DEVICE)
#define STATE_ALLOWS_REBOOT       (STATE_ALLOWS_ERASE|STATE_PROTO_PROG_MULTI|STATE_PROTO_GET_CRC)
#  define SET_BL_STATE(s)         bl_state |= (s)
#  define SET_BL_FIRST_STATE(s)   bl_state |= (s)
#endif
/*----------------------------------typedef-----------------------------------*/

/*---------------------------------prototype----------------------------------*/

/*----------------------------------variable----------------------------------*/
static uint8_t bl_type;
static uint8_t last_input;

static const uint32_t    bl_proto_rev = BL_PROTOCOL_VERSION;    // value returned by PROTO_DEVICE_BL_REV

static unsigned head, tail;
static uint8_t rx_buf[256] USB_DATA_ALIGN;

static enum led_state {LED_BLINK, LED_ON, LED_OFF} _led_state;

volatile unsigned timer[NTIMERS];
/*-------------------------------------os-------------------------------------*/

/*----------------------------------function----------------------------------*/
inline void cinit(void *config, uint8_t interface)
{
#if INTERFACE_USB

    if (interface == USB) {
        return usb_cinit(config);
    }

#endif
#if INTERFACE_USART

    if (interface == USART) {
        return uart_cinit(config);
    }

#endif
}
inline void cfini(void)
{
#if INTERFACE_USB
    usb_cfini();
#endif
#if INTERFACE_USART
    uart_cfini();
#endif
}
inline int cin(uint32_t devices)
{
#if INTERFACE_USB

    if ((bl_type == NONE || bl_type == USB) && (devices & USB0_DEV) != 0) {
        int usb_in = usb_cin();

        if (usb_in >= 0) {
            last_input = USB;
            return usb_in;
        }
    }

#endif

#if INTERFACE_USART

    if ((bl_type == NONE || bl_type == USART) && (devices & SERIAL0_DEV) != 0) {
        int    uart_in = uart_cin();

        if (uart_in >= 0) {
            last_input = USART;
            return uart_in;
        }
    }

#endif

    return -1;
}

inline void cout(uint8_t *buf, unsigned len)
{
#if INTERFACE_USB

    if (bl_type == USB) {
        usb_cout(buf, len);
    }

#endif
#if INTERFACE_USART

    if (bl_type == USART) {
        uart_cout(buf, len);
    }

#endif
}

void
buf_put(uint8_t b)
{
    unsigned next = (head + 1) % sizeof(rx_buf);

    if (next != tail) {
        rx_buf[head] = b;
        head = next;
    }
}

int
buf_get(void)
{
    int ret = -1;

    if (tail != head) {
        ret = rx_buf[tail];
        tail = (tail + 1) % sizeof(rx_buf);
    }

    return ret;
}

static void
do_jump(uint32_t stacktop, uint32_t entrypoint)
{
    __asm volatile(
        "msr msp, %0    \n"
        "bx    %1    \n"
        : : "r"(stacktop), "r"(entrypoint) :);

    // just to keep noreturn happy
    for (;;) ;
}

void
jump_to_app()
{
    const uint32_t *app_base = (const uint32_t *)APP_LOAD_ADDRESS;
    const uint32_t *vec_base = (const uint32_t *)app_base;

    /*
     * We refuse to program the first word of the app until the upload is marked
     * complete by the host.  So if it's not 0xffffffff, we should try booting it.
     */
    if (app_base[0] == 0xffffffff) {
        return;
    }

#ifdef SECURE_BTL_ENABLED
    const image_toc_entry_t *toc_entries;
    uint8_t len;
    uint8_t i = 0;

    /* When secure btl is used, the address comes from the TOC */
    app_base = (const uint32_t *)0;
    vec_base = (const uint32_t *)0;

    /* TOC not found or empty, stay in btl */
    if (!find_toc(&toc_entries, &len)) {
        return;
    }

    /* Verify the first entry, containing the TOC itself */
    if (!verify_app(0, toc_entries)) {
        /* Image verification failed, stay in btl */
        return;
    }

    /* TOC is verified, loop through all the apps and perform crypto ops */
    for (i = 0; i < len; i++) {
        /* Verify app, if needed. i == 0 is already verified */
        if (i != 0 &&
            toc_entries[i].flags1 & TOC_FLAG1_CHECK_SIGNATURE &&
            !verify_app(i, toc_entries)) {
            /* Signature check failed, don't process this app */
            continue;
        }

        /* Check if this app needs decryption */
        if (toc_entries[i].flags1 & TOC_FLAG1_DECRYPT &&
            !decrypt_app(i, toc_entries)) {
            /* Decryption / authenticated decryption failed, skip this app */
            continue;
        }

        /* Check if this app needs to be copied to RAM */
        if (toc_entries[i].flags1 & TOC_FLAG1_COPY) {
            /* TOC is verified, so we assume that the addresses are good */
            memcpy(toc_entries[i].target, toc_entries[i].start,
                   (uintptr_t)toc_entries[i].end - (uintptr_t)toc_entries[i].start);
        }

        /* Check if this app is bootable, if so set the app_base */
        if (toc_entries[i].flags1 & TOC_FLAG1_BOOT) {
            app_base = get_base_addr(&toc_entries[i]);
        }

        /* Check if this app has vectors, if so set the vec_base */
        if (toc_entries[i].flags1 & TOC_FLAG1_VTORS) {
            vec_base = get_base_addr(&toc_entries[i]);
        }
    }

    if (app_base == 0) {
        /* No bootable app found in TOC, bail out */
        return;
    }

    if (vec_base == 0) {
        /* No separate vectors block, vectors come along with the app */
        vec_base = app_base;
    }

#endif

    /*
     * The second word of the app is the entrypoint; it must point within the
     * flash area (or we have a bad flash).
     */
    if (app_base[1] < APP_LOAD_ADDRESS) {
        return;
    }

    if (app_base[1] >= (APP_LOAD_ADDRESS + board_info.fw_size)) {
        return;
    }

    /* just for paranoia's sake */
    arch_flash_lock();

    /* deinitialise the interface */
    cfini();

    /* reset the clock */
    clock_deinit();

    /* deinitialise the board */
    board_deinit();

    /* switch exception handlers to the application */
    arch_setvtor((uint32_t)vec_base);

    /* extract the stack and entrypoint from the app vector table and go */
    do_jump(app_base[0], app_base[1]);
}

void
sys_tick_handler(void)
{
    unsigned i;

    for (i = 0; i < NTIMERS; i++)
        if (timer[i] > 0) {
            timer[i]--;
        }

    if ((_led_state == LED_BLINK) && (timer[TIMER_LED] == 0)) {
        led_toggle(LED_BOOTLOADER);
        timer[TIMER_LED] = 50;
    }
}

void
delay(unsigned msec)
{
    timer[TIMER_DELAY] = msec;

    while (timer[TIMER_DELAY] > 0)
        ;
}

static void
led_set(enum led_state state)
{
    _led_state = state;

    switch (state) {
    case LED_OFF:
        led_off(LED_BOOTLOADER);
        break;

    case LED_ON:
        led_on(LED_BOOTLOADER);
        break;

    case LED_BLINK:
        /* restart the blink state machine ASAP */
        timer[TIMER_LED] = 0;
        break;
    }
}

static void
sync_response(void)
{
    uint8_t data[] = {
        PROTO_INSYNC,    // "in sync"
        PROTO_OK    // "OK"
    };

    cout(data, sizeof(data));
}

static void
invalid_response(void)
{
    uint8_t data[] = {
        PROTO_INSYNC,    // "in sync"
        PROTO_INVALID    // "invalid command"

    };

    cout(data, sizeof(data));
}

static void
failure_response(void)
{
    uint8_t data[] = {
        PROTO_INSYNC,    // "in sync"
        PROTO_FAILED    // "command failed"
    };

    cout(data, sizeof(data));
}

static int
cin_wait(unsigned timeout)
{
    int c = -1;

    /* start the timeout */
    timer[TIMER_CIN] = timeout;

    do {
        c = cin(board_get_devices());

        if (c >= 0) {
            break;
        }

    } while (timer[TIMER_CIN] > 0);

    return c;
}

/**
 * Function to wait for EOC
 *
 * @param timeout length of time in ms to wait for the EOC to be received
 * @return true if the EOC is returned within the timeout perio, else false
 */
inline static bool
wait_for_eoc(unsigned timeout)
{
    return cin_wait(timeout) == PROTO_EOC;
}

static void
cout_word(uint32_t val)
{
    cout((uint8_t *)&val, 4);
}

#ifndef NO_OTP_SN_CHIP
static int
cin_word(uint32_t *wp, unsigned timeout)
{
    union {
        uint32_t w;
        uint8_t b[4];
    } u;

    for (unsigned i = 0; i < 4; i++) {
        int c = cin_wait(timeout);

        if (c < 0) {
            return c;
        }

        u.b[i] = c & 0xff;
    }

    *wp = u.w;
    return 0;
}
#endif

static uint32_t
crc32(const uint8_t *src, unsigned len, unsigned state)
{
    static uint32_t crctab[256];

    /* check whether we have generated the CRC table yet */
    /* this is much smaller than a static table */
    if (crctab[1] == 0) {
        for (unsigned i = 0; i < 256; i++) {
            uint32_t c = i;

            for (unsigned j = 0; j < 8; j++) {
                if (c & 1) {
                    c = 0xedb88320U ^ (c >> 1);

                } else {
                    c = c >> 1;
                }
            }

            crctab[i] = c;
        }
    }

    for (unsigned i = 0; i < len; i++) {
        state = crctab[(state ^ src[i]) & 0xff] ^ (state >> 8);
    }

    return state;
}

void
bootloader(unsigned timeout)
{
    bl_type = NONE; // The type of the bootloader, whether loading from USB or USART, will be determined by on what port the bootloader recevies its first valid command.
    volatile uint32_t  bl_state = 0; // Must see correct command sequence to erase and reboot (commit first word)
    uint32_t    address = board_info.fw_size;    /* force erase before upload will work */
    uint32_t    first_word = 0xffffffff;

    /* if we are working with a timeout, start it running */
    if (timeout) {
        timer[TIMER_BL_WAIT] = timeout;
    }

    /* make the LED blink while we are idle */
    led_set(LED_BLINK);

    while (true) {
        volatile int c;
        int arg;
        static union {
            uint8_t     c[256];
            uint32_t    w[64];
        } flash_buffer;

        // Wait for a command byte
        led_off(LED_ACTIVITY);

        do {
            /* if we have a timeout and the timer has expired, return now */
            if (timeout && !timer[TIMER_BL_WAIT]) {
                return;
            }

            /* try to get a byte from the host */
            c = cin_wait(0);

        } while (c < 0);

        led_on(LED_ACTIVITY);

        // handle the command byte
        switch (c) {

        // sync
        //
        // command:        GET_SYNC/EOC
        // reply:        INSYNC/OK
        //
        case PROTO_GET_SYNC:

            /* expect EOC */
            if (!wait_for_eoc(2)) {
                goto cmd_bad;
            }

            SET_BL_FIRST_STATE(STATE_PROTO_GET_SYNC);
            break;

        // get device info
        //
        // command:        GET_DEVICE/<arg:1>/EOC
        // BL_REV reply:    <revision:4>/INSYNC/EOC
        // BOARD_ID reply:    <board type:4>/INSYNC/EOC
        // BOARD_REV reply:    <board rev:4>/INSYNC/EOC
        // FW_SIZE reply:    <firmware size:4>/INSYNC/EOC
        // VEC_AREA reply    <vectors 7-10:16>/INSYNC/EOC
        // bad arg reply:    INSYNC/INVALID
        //
        case PROTO_GET_DEVICE:
            /* expect arg then EOC */
            arg = cin_wait(1000);

            if (arg < 0) {
                goto cmd_bad;
            }

            if (!wait_for_eoc(2)) {
                goto cmd_bad;
            }

            switch (arg) {
            case PROTO_DEVICE_BL_REV:
                cout((uint8_t *)&bl_proto_rev, sizeof(bl_proto_rev));
                break;

            case PROTO_DEVICE_BOARD_ID:
                cout((uint8_t *)&board_info.board_type, sizeof(board_info.board_type));
                break;

            case PROTO_DEVICE_BOARD_REV:
                cout((uint8_t *)&board_info.board_rev, sizeof(board_info.board_rev));
                break;

            case PROTO_DEVICE_FW_SIZE:
                cout((uint8_t *)&board_info.fw_size, sizeof(board_info.fw_size));
                break;

            case PROTO_DEVICE_VEC_AREA:
                for (unsigned p = 7; p <= 10; p++) {
                    uint32_t bytes = flash_func_read_word(p * 4);

                    cout((uint8_t *)&bytes, sizeof(bytes));
                }

                break;

            default:
                goto cmd_bad;
            }

            SET_BL_STATE(STATE_PROTO_GET_DEVICE);
            break;

        // erase and prepare for programming
        //
        // command:        ERASE/EOC
        // success reply:    INSYNC/OK
        // erase failure:    INSYNC/FAILURE
        //
        case PROTO_CHIP_ERASE:

            /* expect EOC */
            if (!wait_for_eoc(2)) {
                goto cmd_bad;
            }

            if ((bl_state & STATE_ALLOWS_ERASE) != STATE_ALLOWS_ERASE) {
                goto cmd_bad;
            }

            // clear the bootloader LED while erasing - it stops blinking at random
            // and that's confusing
            led_set(LED_ON);

            // erase all sectors
            arch_flash_unlock();

            for (int i = 0; flash_func_sector_size(i) != 0; i++) {
                flash_func_erase_sector(i);
            }

            // disable the LED while verifying the erase
            led_set(LED_OFF);

            // verify the erase
            for (address = 0; address < board_info.fw_size; address += 4)
                if (flash_func_read_word(address) != 0xffffffff) {
                    goto cmd_fail;
                }

            address = 0;
            SET_BL_STATE(STATE_PROTO_CHIP_ERASE);

            // resume blinking
            led_set(LED_BLINK);
            break;

        // program bytes at current address
        //
        // command:        PROG_MULTI/<len:1>/<data:len>/EOC
        // success reply:    INSYNC/OK
        // invalid reply:    INSYNC/INVALID
        // readback failure:    INSYNC/FAILURE
        //
        case PROTO_PROG_MULTI:        // program bytes
            // expect count
            arg = cin_wait(50);

            if (arg < 0) {
                goto cmd_bad;
            }

            // sanity-check arguments
            if (arg % 4) {
                goto cmd_bad;
            }

            if ((address + arg) > board_info.fw_size) {
                goto cmd_bad;
            }

            if ((unsigned int)arg > sizeof(flash_buffer.c)) {
                goto cmd_bad;
            }

            for (int i = 0; i < arg; i++) {
                c = cin_wait(1000);

                if (c < 0) {
                    goto cmd_bad;
                }

                flash_buffer.c[i] = c;
            }

            if (!wait_for_eoc(200)) {
                goto cmd_bad;
            }

            if (address == 0) {
                // save the first word and don't program it until everything else is done
                first_word = flash_buffer.w[0];
                // replace first word with bits we can overwrite later
                flash_buffer.w[0] = 0xffffffff;
            }

            arg /= 4;

            for (int i = 0; i < arg; i++) {

                // program the word
                flash_func_write_word(address, flash_buffer.w[i]);

                // do immediate read-back verify
                if (flash_func_read_word(address) != flash_buffer.w[i]) {
                    goto cmd_fail;
                }

                address += 4;
            }

            SET_BL_STATE(STATE_PROTO_PROG_MULTI);

            break;

        // fetch CRC of the entire flash area
        //
        // command:            GET_CRC/EOC
        // reply:            <crc:4>/INSYNC/OK
        //
        case PROTO_GET_CRC:

            // expect EOC
            if (!wait_for_eoc(2)) {
                goto cmd_bad;
            }

            // compute CRC of the programmed area
            uint32_t sum = 0;

            for (unsigned p = 0; p < board_info.fw_size; p += 4) {
                uint32_t bytes;

                if ((p == 0) && (first_word != 0xffffffff)) {
                    bytes = first_word;

                } else {
                    bytes = flash_func_read_word(p);
                }

                sum = crc32((uint8_t *)&bytes, sizeof(bytes), sum);
            }

            cout_word(sum);
            SET_BL_STATE(STATE_PROTO_GET_CRC);
            break;

#ifndef NO_OTP_SN_CHIP

        // read a word from the OTP
        //
        // command:            GET_OTP/<addr:4>/EOC
        // reply:            <value:4>/INSYNC/OK
        case PROTO_GET_OTP:
            // expect argument
            {
                uint32_t index = 0;

                if (cin_word(&index, 100)) {
                    goto cmd_bad;
                }

                // expect EOC
                if (!wait_for_eoc(2)) {
                    goto cmd_bad;
                }

                cout_word(flash_func_read_otp(index));
            }
            break;

        // read the SN from the UDID
        //
        // command:            GET_SN/<addr:4>/EOC
        // reply:            <value:4>/INSYNC/OK
        case PROTO_GET_SN:
            // expect argument
            {
                uint32_t index = 0;

                if (cin_word(&index, 100)) {
                    goto cmd_bad;
                }

                // expect EOC
                if (!wait_for_eoc(2)) {
                    goto cmd_bad;
                }

                // expect valid indices 0, 4 ...ARCH_SN_MAX_LENGTH-4

                if (index % sizeof(uint32_t) != 0 || index > ARCH_SN_MAX_LENGTH - sizeof(uint32_t)) {
                    goto cmd_bad;
                }

                cout_word(flash_func_read_sn(index));
            }

            SET_BL_STATE(STATE_PROTO_GET_SN);
            break;

        // read the chip ID code
        //
        // command:            GET_CHIP/EOC
        // reply:            <value:4>/INSYNC/OK
        case PROTO_GET_CHIP: {
                // expect EOC
                if (!wait_for_eoc(2)) {
                    goto cmd_bad;
                }

                cout_word(get_mcu_id());
                SET_BL_STATE(STATE_PROTO_GET_CHIP);
            }
            break;

        // read the chip  description
        //
        // command:            GET_CHIP_DES/EOC
        // reply:            <value:4>/INSYNC/OK
        case PROTO_GET_CHIP_DES: {
                uint8_t buffer[MAX_DES_LENGTH];
                unsigned len = MAX_DES_LENGTH;

                // expect EOC
                if (!wait_for_eoc(2)) {
                    goto cmd_bad;
                }

                len = get_mcu_desc(len, buffer);
                cout_word(len);
                cout(buffer, len);
                SET_BL_STATE(STATE_PROTO_GET_CHIP_DES);
            }
            break;
#endif

#ifdef BOOT_DELAY_ADDRESS

        case PROTO_SET_DELAY: {
                /*
                  Allow for the bootloader to setup a
                  boot delay signature which tells the
                  board to delay for at least a
                  specified number of seconds on boot.
                 */
                int v = cin_wait(100);

                if (v < 0) {
                    goto cmd_bad;
                }

                uint8_t boot_delay = v & 0xFF;

                if (boot_delay > BOOT_DELAY_MAX) {
                    goto cmd_bad;
                }

                // expect EOC
                if (!wait_for_eoc(2)) {
                    goto cmd_bad;
                }

                uint32_t sig1 = flash_func_read_word(BOOT_DELAY_ADDRESS);
                uint32_t sig2 = flash_func_read_word(BOOT_DELAY_ADDRESS + 4);

                if (sig1 != BOOT_DELAY_SIGNATURE1 ||
                    sig2 != BOOT_DELAY_SIGNATURE2) {
                    goto cmd_bad;
                }

                uint32_t value = (BOOT_DELAY_SIGNATURE1 & 0xFFFFFF00) | boot_delay;
                flash_func_write_word(BOOT_DELAY_ADDRESS, value);

                if (flash_func_read_word(BOOT_DELAY_ADDRESS) != value) {
                    goto cmd_fail;
                }
            }
            break;
#endif

        // finalise programming and boot the system
        //
        // command:            BOOT/EOC
        // reply:            INSYNC/OK
        //
        case PROTO_BOOT:

            // expect EOC
            if (!wait_for_eoc(1000)) {
                goto cmd_bad;
            }

            if (first_word != 0xffffffff && (bl_state & STATE_ALLOWS_REBOOT) != STATE_ALLOWS_REBOOT) {
                goto cmd_bad;
            }

            // program the deferred first word
            if (first_word != 0xffffffff) {
                flash_func_write_word(0, first_word);

                if (flash_func_read_word(0) != first_word) {
                    goto cmd_fail;
                }

                // revert in case the flash was bad...
                first_word = 0xffffffff;
            }

            // send a sync and wait for it to be collected
            sync_response();
            delay(100);

            // quiesce and jump to the app
            return;

        case PROTO_DEBUG:
            // XXX reserved for ad-hoc debugging as required
            break;

        default:
            continue;
        }

        // we got a command worth syncing, so kill the timeout because
        // we are probably talking to the uploader
        timeout = 0;

        // Set the bootloader port based on the port from which we received the first valid command
        if (bl_type == NONE) {
            bl_type = last_input;
        }

        // send the sync response for this command
        sync_response();
        continue;
cmd_bad:
        // send an 'invalid' response but don't kill the timeout - could be garbage
        invalid_response();
        bl_state = 0;
        continue;

cmd_fail:
        // send a 'command failed' response but don't kill the timeout - could be garbage
        failure_response();
        continue;
    }
}
/*------------------------------------test------------------------------------*/


